import numpy as np
import argparse
from scipy import io, spatial, linalg
from sklearn.metrics import confusion_matrix
from sklearn.decomposition import PCA

parser = argparse.ArgumentParser(description="Enhanced SAE with Feature Fusion and Triplet Loss")

parser.add_argument('-data', '--dataset', help='choose between APY, AWA2, AWA1, CUB, SUN', default='AWA2', type=str)
parser.add_argument('-mode', '--mode', help='train/test, if test set alpha, gamma to best values below', default='train', type=str)
parser.add_argument('-ld1', '--ld1', default=5, help='best value for F-->S during test, lower bound of variation interval during train', type=float)
parser.add_argument('-ld2', '--ld2', default=5, help='best value for S-->F during test, upper bound of variation interval during train', type=float)
parser.add_argument('-l2', '--l2_reg', default=0.01, help='L2 regularization coefficient', type=float)
parser.add_argument('-alpha', '--fusion_alpha', default=0.7, help='weight for visual features in multimodal fusion', type=float)
parser.add_argument('-tw', '--triplet_weight', default=0.5, help='weight for triplet loss in the objective function', type=float)
parser.add_argument('-pca', '--pca_components', default=1.0, help='variance to preserve in PCA preprocessing, 1.0 means no PCA', type=float)


class SAE():
	"""语义自编码器（Semantic AutoEncoder）类
	
	实现了基于Kodirov等人在CVPR 2017提出的语义自编码器模型，用于零样本学习任务。
	该模型通过学习视觉特征空间和语义空间之间的映射关系，实现对zsl未见类别的识别。
	"""

	def __init__(self, args):
		"""初始化SAE模型
		
		加载数据集，准备训练、验证和测试数据
		
		参数:
		    args: 包含数据集名称、模式和lambda参数范围的参数对象
		"""
		self.args = args

		# 加载数据集文件
		data_folder = 'xlsa17/data/'+args.dataset+'/'
		res101 = io.loadmat(data_folder+'res101.mat')  # 加载ResNet101特征
		att_splits=io.loadmat(data_folder+'att_splits.mat')  # 加载属性和数据集划分

		# 定义数据集划分位置
		train_loc = 'train_loc'  # 训练集索引
		val_loc = 'val_loc'  # 验证集索引
		test_loc = 'test_unseen_loc'  # 测试集索引（未见类别）

		# 提取特征
		feat = res101['features']  # 原始特征矩阵，形状为(d x N)，d为特征维度，N为样本数量
		# 根据数据集划分提取对应的特征
		self.X_train = feat[:, np.squeeze(att_splits[train_loc]-1)]  # 训练集特征
		self.X_val = feat[:, np.squeeze(att_splits[val_loc]-1)]  # 验证集特征
		self.X_test = feat[:, np.squeeze(att_splits[test_loc]-1)]  # 测试集特征

		print('Tr:{}; Val:{}; Ts:{}\n'.format(self.X_train.shape[1], self.X_val.shape[1], self.X_test.shape[1]))

		# 提取标签
		labels = res101['labels']  # 原始标签
		self.labels_train = labels[np.squeeze(att_splits[train_loc]-1)]  # 训练集标签
		self.labels_val = labels[np.squeeze(att_splits[val_loc]-1)]  # 验证集标签
		self.labels_test = labels[np.squeeze(att_splits[test_loc]-1)]  # 测试集标签

		# 获取唯一的类别标签
		train_labels_seen = np.unique(self.labels_train)  # 训练集中的可见类别
		val_labels_unseen = np.unique(self.labels_val)  # 验证集中的未见类别
		test_labels_unseen = np.unique(self.labels_test)  # 测试集中的未见类别

		# 将标签重新编码为从0开始的连续整数
		i=0
		for labels in train_labels_seen:
			self.labels_train[self.labels_train == labels] = i
			i+=1

		j=0
		for labels in val_labels_unseen:
			self.labels_val[self.labels_val == labels] = j
			j+=1

		k=0
		for labels in test_labels_unseen:
			self.labels_test[self.labels_test == labels] = k
			k+=1

		# 提取语义特征（属性向量）
		sig = att_splits['att']  # 属性矩阵，形状为(k x C)，k为属性数量，C为类别总数
		self.train_sig = sig[:, train_labels_seen-1]  # 训练集类别的属性
		self.val_sig = sig[:, val_labels_unseen-1]  # 验证集类别的属性
		self.test_sig = sig[:, test_labels_unseen-1]  # 测试集类别的属性

		# 为每个训练样本分配对应类别的属性向量
		self.train_att = np.zeros((self.X_train.shape[1], self.train_sig.shape[0]))
		for i in range(self.train_att.shape[0]):
		    self.train_att[i] = self.train_sig.T[self.labels_train[i][0]]

		# 特征预处理增强
		# 1. 多模态特征融合（如果有文本特征）
		# 注意：这里假设可能存在文本特征，实际使用时需要加载或生成文本特征
		try:
			# 尝试加载文本特征（如果存在）
			text_features_path = data_folder+'text_features.mat'
			text_feat = io.loadmat(text_features_path)
			text_train = text_feat['text_features'][:, np.squeeze(att_splits[train_loc]-1)]
			text_val = text_feat['text_features'][:, np.squeeze(att_splits[val_loc]-1)]
			text_test = text_feat['text_features'][:, np.squeeze(att_splits[test_loc]-1)]
			
			# 融合ResNet特征与文本特征
			self.X_train = self.fuse_features(self.X_train, text_train)
			self.X_val = self.fuse_features(self.X_val, text_val)
			self.X_test = self.fuse_features(self.X_test, text_test)
			print('多模态特征融合完成: 视觉特征 + 文本特征')
		except:
			print('未找到文本特征，仅使用视觉特征')
		
		# 2. 对抗去噪预处理（使用PCA去除特征噪声）
		if self.args.pca_components == 1.0:
			# 当pca_components为1.0时，跳过PCA处理，保持原始特征不变
			print('跳过PCA预处理，使用原始特征')
		else:
			pca = PCA(n_components=self.args.pca_components)  # 通过命令行参数调整方差保留比例
			self.X_train = pca.fit_transform(self.X_train.T).T
			self.X_val = pca.transform(self.X_val.T).T
			self.X_test = pca.transform(self.X_test.T).T
			print('PCA对抗去噪预处理完成，保留方差:{}'.format(self.args.pca_components))
		
		# 对训练特征进行L2归一化
		self.X_train = self.normalizeFeature(self.X_train.T).T

	def fuse_features(self, visual_feat, text_feat, alpha=1.0):
		"""加权融合视觉与文本特征
		
		将视觉特征和文本特征进行归一化后加权融合，提高特征的表达能力。
		
		参数:
		    visual_feat: 视觉特征矩阵，形状为(d x N)
		    text_feat: 文本特征矩阵，形状为(d' x N)
		    alpha: 视觉特征的权重，默认为1.0
		    
		返回:
		    融合后的特征矩阵，形状为(d x N)或(d' x N)
		"""
		# 转置特征以便于归一化
		visual_norm = self.normalizeFeature(visual_feat.T).T
		text_norm = self.normalizeFeature(text_feat.T).T
		
		# 如果维度不同，需要进行调整（例如使用PCA）
		if visual_norm.shape[0] != text_norm.shape[0]:
			# 选择维度较小的作为目标维度
			target_dim = min(visual_norm.shape[0], text_norm.shape[0])
			if visual_norm.shape[0] > target_dim:
				pca = PCA(n_components=target_dim)
				visual_norm = pca.fit_transform(visual_norm.T).T
			if text_norm.shape[0] > target_dim:
				pca = PCA(n_components=target_dim)
				text_norm = pca.fit_transform(text_norm.T).T
		
		# 加权融合
		return alpha * visual_norm + (1 - alpha) * text_norm

	def triplet_loss(self, S_pred, S_true, margin=0.2, labels=None):
		"""改进的三重损失函数
		
		通过拉近同类样本，推远异类样本，增强模型的判别能力。
		使用样本的类别标签来选择正负样本，确保锚点与正样本来自同一类别，而负样本来自不同类别。
		
		参数:
		    S_pred: 预测的语义特征
		    S_true: 真实的语义特征
		    margin: 边界值，控制正负样本之间的距离
		    labels: 样本的类别标签，如果为None则使用相邻样本作为正负样本
		    
		返回:
		    三重损失值
		"""
		if labels is None:
			# 如果没有提供标签，则使用原始的相邻样本策略（向后兼容）
			anchor = S_pred[:-1]
			positive = S_true[:-1]
			negative = S_true[1:]
			
			# 计算锚点与正样本的距离
			pos_dist = np.sum((anchor - positive)**2, axis=1)
			# 计算锚点与负样本的距离
			neg_dist = np.sum((anchor - negative)**2, axis=1)
			# 计算三重损失
			loss = np.mean(np.maximum(0, pos_dist - neg_dist + margin))
		else:
			# 使用标签信息选择正负样本
			n_samples = S_pred.shape[0]
			triplet_losses = []
			
			# 对每个样本构建三元组
			for i in range(n_samples):
				anchor_feat = S_pred[i]
				anchor_label = labels[i][0]
				
				# 找出所有与锚点同类的样本作为候选正样本
				pos_indices = [j for j in range(n_samples) if j != i and labels[j][0] == anchor_label]
				# 找出所有与锚点不同类的样本作为候选负样本
				neg_indices = [j for j in range(n_samples) if labels[j][0] != anchor_label]
				
				# 如果没有足够的正样本或负样本，则跳过
				if not pos_indices or not neg_indices:
					continue
				
				# 硬三元组挖掘：选择最难区分的样本对
				# 1. 找到最难的正样本（与锚点距离最远的同类样本）
				hardest_pos_idx = max(pos_indices, key=lambda j: np.sum((anchor_feat - S_true[j])**2))
				# 2. 找到最难的负样本（与锚点距离最近的异类样本）
				hardest_neg_idx = min(neg_indices, key=lambda j: np.sum((anchor_feat - S_true[j])**2))
				
				# 计算与最难正样本的距离
				pos_dist = np.sum((anchor_feat - S_true[hardest_pos_idx])**2)
				# 计算与最难负样本的距离
				neg_dist = np.sum((anchor_feat - S_true[hardest_neg_idx])**2)
				
				# 计算当前样本的三重损失
				triplet_loss = max(0, pos_dist - neg_dist + margin)
				triplet_losses.append(triplet_loss)
			
			# 如果没有有效的三元组，则返回0
			if not triplet_losses:
				return 0.0
			
			# 计算平均三重损失
			loss = np.mean(triplet_losses)
		
		return loss

	def normalizeFeature(self, x):
		"""对特征进行L2归一化
		
		将输入特征向量归一化为单位长度（L2范数为1），以便后续计算余弦相似度。
		归一化后的特征可以减少不同样本之间的尺度差异，提高模型性能。
		
		参数:
		    x: 形状为(N x d)的特征矩阵，N为样本数量，d为特征维度
		    
		返回:
		    归一化后的特征矩阵，每行的L2范数为1
		"""
		# 添加小常数避免除零错误
		x = x + 1e-10
		# 计算每个样本的L2范数（欧几里得范数）
		feature_norm = np.sum(x**2, axis=1)**0.5 # l2-norm
		# 将每个特征向量除以其L2范数，实现归一化
		feat = x / feature_norm[:, np.newaxis]

		return feat

	def find_W(self, X, S, ld, l2_reg=0.01, triplet_weight=0.5):
		"""计算投影矩阵W
		
		通过求解Sylvester方程来优化投影矩阵W，使得投影后的特征与语义属性尽可能接近。
		该方法是SAE模型的核心，实现了特征空间和语义空间之间的双向映射。
		
		数学公式：AW + WB = C，其中：
		- A = S·S^T
		- B = λ·X·X^T
		- C = (1+λ)·S·X^T
		
		添加了L2正则化项和三重损失约束，以防止过拟合并提高模型泛化能力。
		
		参数:
		    X: 形状为(d x N)的数据矩阵，d为特征维度，N为样本数量
		    S: 形状为(k x N)的语义矩阵，k为属性数量，N为样本数量
		    ld: 正则化参数lambda，控制重建误差和投影误差之间的平衡
		    l2_reg: L2正则化系数，控制W的范数大小，默认为0.01
		    triplet_weight: 三重损失的权重，默认为0.5
		    
		返回:
		    W: 形状为(k x d)的投影矩阵
		"""
		# 计算Sylvester方程的系数矩阵
		A = np.dot(S, S.T) + l2_reg * np.eye(S.shape[0])  # 形状为(k x k)，添加L2正则化
		B = ld*np.dot(X, X.T)  # 形状为(d x d)
		C = (1+ld)*np.dot(S, X.T)  # 形状为(k x d)
		
		# 求解Sylvester方程：AW + WB = C
		W = linalg.solve_sylvester(A, B, C)  # 形状为(k x d)
		
		# 使用迭代优化进一步改进W，加入三重损失约束
		if triplet_weight > 0:
			# 初始化投影矩阵
			W_opt = np.copy(W)
			# 学习率
			lr = 0.001
			# 迭代次数
			iterations = 50
			
			for _ in range(iterations):
				# 计算投影后的特征
				S_pred = np.dot(W_opt, X)
				
				# 计算三重损失
				triplet_loss_val = self.triplet_loss(S_pred, S)
				
				# 计算重建损失
				reconstruction_loss = np.sum((np.dot(W_opt, X) - S)**2)
				
				# 计算L2正则化损失
				l2_loss = l2_reg * np.sum(W_opt**2)
				
				# 计算总损失
				total_loss = reconstruction_loss + l2_loss + triplet_weight * triplet_loss_val
				
				# 计算梯度并更新W
				grad = 2 * (np.dot(np.dot(W_opt, X) - S, X.T) + l2_reg * W_opt)
				W_opt -= lr * grad
				
			# 如果优化后的损失更小，则使用优化后的W
			S_pred_orig = np.dot(W, X)
			orig_loss = np.sum((S_pred_orig - S)**2) + l2_reg * np.sum(W**2)
			
			S_pred_opt = np.dot(W_opt, X)
			opt_loss = np.sum((S_pred_opt - S)**2) + l2_reg * np.sum(W_opt**2) + triplet_weight * self.triplet_loss(S_pred_opt, S)
			
			if opt_loss < orig_loss:
				W = W_opt

		return W

	def find_lambda(self):
		"""搜索最佳的lambda参数
		
		通过在验证集上评估不同lambda值的性能，为F→S和S→F两个方向分别找到最佳的lambda参数。
		lambda是一个正则化参数，控制重建误差和投影误差之间的平衡。
		
		搜索过程：
		1. 根据不同数据集设置不同的lambda搜索范围
		2. 对每个lambda值，计算投影矩阵W并在验证集上评估性能
		3. 分别记录F→S和S→F方向上性能最好的lambda值和对应的W
		
		返回:
		    best_W_F2S: F→S方向上性能最好的投影矩阵
		    best_W_S2F: S→F方向上性能最好的投影矩阵
		"""
		print('Training...\n')

		# 初始化最佳准确率和对应的参数
		best_acc_F2S = 0.0  # F→S方向的最佳准确率
		best_acc_S2F = 0.0  # S→F方向的最佳准确率

		# 根据不同数据集设置不同的搜索策略
		dataset = self.args.dataset.upper()
		
		if dataset == 'CUB':
			# CUB数据集需要更大范围的lambda搜索
			lambda_values_F2S = [50, 100, 200, 500, 1000, 2000, 5000]
			lambda_values_S2F = [0.05, 0.1, 0.2, 0.5, 1, 2, 5]
		elif dataset == 'SUN':
			lambda_values_F2S = [0.005, 0.01, 0.02, 0.05, 0.1, 0.16, 0.2, 0.32, 0.5, 1, 2, 5]
			lambda_values_S2F = [0.005, 0.01, 0.02, 0.05, 0.08, 0.1, 0.16, 0.2, 0.5, 1]
		elif dataset == 'APY':
			lambda_values_F2S = [0.5, 1, 1.5, 2, 3, 4, 5, 10, 20, 50]
			lambda_values_S2F = [0.5, 1, 2, 3, 4, 5, 10, 20, 50]
		elif dataset == 'AWA1':
			lambda_values_F2S = [2, 2.5, 3, 3.5, 4, 5, 6, 7, 8]
			lambda_values_S2F = [0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6]
		else:  # AWA2或其他数据集
			lambda_values_F2S = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.2, 1.4, 1.6]
			lambda_values_S2F = [0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.2, 1.6]
		
		# 获取三重损失权重
		triplet_weight = self.args.triplet_weight
		l2_reg = self.args.l2_reg
		
		# 先搜索F2S方向的最佳lambda
		print('\nSearching best lambda for F-->S direction...')
		for ld in lambda_values_F2S:
			# 计算当前lambda值下的投影矩阵，加入三重损失约束
			W = self.find_W(self.X_train, self.train_att.T, ld, l2_reg=l2_reg, triplet_weight=triplet_weight)
			# 在验证集上评估当前投影矩阵的F2S性能
			acc_F2S = self.zsl_acc(self.X_val, W, self.labels_val, self.val_sig, 'F2S')
			print('Val Acc [F-->S]: {} @ lambda = {}'.format(acc_F2S, ld))
			
			# 更新F→S方向的最佳参数
			if acc_F2S > best_acc_F2S:
				best_acc_F2S = acc_F2S
				lambda_F2S = ld
				best_W_F2S = np.copy(W)
		
		# 再搜索S2F方向的最佳lambda
		print('\nSearching best lambda for S-->F direction...')
		for ld in lambda_values_S2F:
			# 计算当前lambda值下的投影矩阵，加入三重损失约束
			W = self.find_W(self.X_train, self.train_att.T, ld, l2_reg=l2_reg, triplet_weight=triplet_weight)
			# 在验证集上评估当前投影矩阵的S2F性能
			acc_S2F = self.zsl_acc(self.X_val, W, self.labels_val, self.val_sig, 'S2F')
			print('Val Acc [S-->F]: {} @ lambda = {}'.format(acc_S2F, ld))
			
			# 更新S→F方向的最佳参数
			if acc_S2F > best_acc_S2F:
				best_acc_S2F = acc_S2F
				lambda_S2F = ld
				best_W_S2F = np.copy(W)

		print('\nBest Val Acc --> [F-->S]:{} @ lambda = {} [S-->F]:{} @ lambda = {}\n'.format(best_acc_F2S, lambda_F2S, best_acc_S2F, lambda_S2F))

		return best_W_F2S, best_W_S2F

	def zsl_acc(self, X, W, y_true, sig, mode='val'): 
		"""计算零样本学习的准确率
		
		评估投影矩阵W在零样本学习任务上的性能
		支持三种模式：
		1. F2S：特征到语义空间的映射（Feature to Semantic）
		2. S2F：语义到特征空间的映射（Semantic to Feature）
		3. val：同时评估两个方向的映射（默认模式）
		
		参数:
		    X: 特征矩阵
		    W: 投影矩阵
		    y_true: 真实标签
		    sig: 语义特征（属性向量）
		    mode: 评估模式，可选值为'F2S'、'S2F'或'val'
		    
		返回:
		    根据mode返回F2S准确率、S2F准确率或两者
		"""
		# 类别平均的Top-1准确率（Class Averaged Top-1 Accuracy）

		if mode=='F2S':
			# [F --> S]，将数据从特征空间投影到语义空间
			# 步骤1：将特征X通过投影矩阵W映射到语义空间
			F2S = np.dot(X.T, self.normalizeFeature(W).T)  # 形状为(N x k)，N为样本数，k为属性数量
			# 步骤2：计算投影后的特征与每个类别的语义特征之间的余弦相似度
			dist_F2S = 1-spatial.distance.cdist(F2S, sig.T, 'cosine')  # 形状为(N x C)，C为类别数量
			# 步骤3：对每个样本，选择相似度最高的类别作为预测结果
			pred_F2S = np.array([np.argmax(y) for y in dist_F2S])
			# 步骤4：计算混淆矩阵并归一化
			cm_F2S = confusion_matrix(y_true, pred_F2S)
			cm_F2S = cm_F2S.astype('float')/cm_F2S.sum(axis=1)[:, np.newaxis]  # 按行归一化
			# 步骤5：计算类别平均准确率（对角线元素的平均值）
			acc_F2S = sum(cm_F2S.diagonal())/sig.shape[1]

			return acc_F2S

		if mode=='S2F':
			# [S --> F]，将数据从语义空间投影到特征空间
			# 步骤1：将语义特征sig通过投影矩阵W映射到特征空间
			S2F = np.dot(sig.T, self.normalizeFeature(W))  # 形状为(C x d)，C为类别数量，d为特征维度
			# 步骤2：计算原始特征与投影后的类别原型之间的余弦相似度
			dist_S2F = 1-spatial.distance.cdist(X.T, self.normalizeFeature(S2F), 'cosine')  # 形状为(N x C)
			# 步骤3：对每个样本，选择相似度最高的类别作为预测结果
			pred_S2F = np.array([np.argmax(y) for y in dist_S2F])
			# 步骤4：计算混淆矩阵并归一化
			cm_S2F = confusion_matrix(y_true, pred_S2F)
			cm_S2F = cm_S2F.astype('float')/cm_S2F.sum(axis=1)[:, np.newaxis]  # 按行归一化
			# 步骤5：计算类别平均准确率
			acc_S2F = sum(cm_S2F.diagonal())/sig.shape[1]

			return acc_S2F

		if mode=='val':
			# 同时评估两个方向的映射
			
			# [F --> S]，将数据从特征空间投影到语义空间
			F2S = np.dot(X.T, self.normalizeFeature(W).T)  # 形状为(N x k)
			dist_F2S = 1-spatial.distance.cdist(F2S, sig.T, 'cosine')  # 形状为(N x C)，计算余弦相似度
			
			# [S --> F]，将数据从语义空间投影到特征空间
			S2F = np.dot(sig.T, self.normalizeFeature(W))  # 形状为(C x d)，C为类别数量，d为特征维度
			dist_S2F = 1-spatial.distance.cdist(X.T, self.normalizeFeature(S2F), 'cosine')  # 形状为(N x C)

			# 对每个样本，选择相似度最高的类别作为预测结果
			pred_F2S = np.array([np.argmax(y) for y in dist_F2S])
			pred_S2F = np.array([np.argmax(y) for y in dist_S2F])

			# 计算F→S方向的混淆矩阵并归一化
			cm_F2S = confusion_matrix(y_true, pred_F2S)
			cm_F2S = cm_F2S.astype('float')/cm_F2S.sum(axis=1)[:, np.newaxis]  # 按行归一化

			# 计算S→F方向的混淆矩阵并归一化
			cm_S2F = confusion_matrix(y_true, pred_S2F)
			cm_S2F = cm_S2F.astype('float')/cm_S2F.sum(axis=1)[:, np.newaxis]  # 按行归一化

			# 计算两个方向的类别平均准确率
			acc_F2S = sum(cm_F2S.diagonal())/sig.shape[1]  # 对角线元素和除以类别数
			acc_S2F = sum(cm_S2F.diagonal())/sig.shape[1]  # 对角线元素和除以类别数

			# 返回两个方向的准确率（而不是取最大值）
			# acc = acc_F2S if acc_F2S>acc_S2F else acc_S2F

			return acc_F2S, acc_S2F

	def evaluate(self):
		"""评估模型性能
		
		根据模式（训练或测试）选择合适的投影矩阵，并在测试集上评估零样本学习性能。
		
		训练模式：通过find_lambda函数搜索最佳lambda参数和对应的投影矩阵
		测试模式：使用指定的lambda参数直接计算投影矩阵
		
		评估过程：
		1. 获取F→S和S→F两个方向的最佳投影矩阵
		2. 在测试集（未见类别）上分别评估两个方向的性能
		3. 输出测试准确率
		"""
		# 获取参数
		l2_reg = self.args.l2_reg
		triplet_weight = self.args.triplet_weight
		print('Using L2 regularization coefficient: {}'.format(l2_reg))
		print('Using triplet loss weight: {}'.format(triplet_weight))
		
		# 根据模式选择投影矩阵
		if self.args.mode=='train': 
			# 训练模式：搜索最佳lambda参数
			best_W_F2S, best_W_S2F = self.find_lambda()
		else:
			# 测试模式：使用指定的lambda参数
			best_W_F2S = self.find_W(self.X_train, self.train_att.T, self.args.ld1, 
			                         l2_reg=l2_reg, triplet_weight=triplet_weight)  # F→S方向
			best_W_S2F = self.find_W(self.X_train, self.train_att.T, self.args.ld2, 
			                         l2_reg=l2_reg, triplet_weight=triplet_weight)  # S→F方向

		# 在测试集上评估F→S方向的性能
		test_acc_F2S = self.zsl_acc(self.X_test, best_W_F2S, self.labels_test, self.test_sig, 'F2S')
		# 在测试集上评估S→F方向的性能
		test_acc_S2F = self.zsl_acc(self.X_test, best_W_S2F, self.labels_test, self.test_sig, 'S2F')

		# 输出测试准确率
		print('Test Acc --> [F-->S]:{} [S-->F]:{}'.format(test_acc_F2S, test_acc_S2F))
		
		# 计算并输出改进幅度（如果有基准值）
		baseline_F2S = {'AWA1': 0.5134, 'AWA2': 0.5166, 'CUB': 0.3948, 'SUN': 0.5285, 'APY': 0.1607}
		baseline_S2F = {'AWA1': 0.5989, 'AWA2': 0.6051, 'CUB': 0.4670, 'SUN': 0.5986, 'APY': 0.1650}
		
		dataset = self.args.dataset.upper()
		if dataset in baseline_F2S and dataset in baseline_S2F:
			improvement_F2S = (test_acc_F2S - baseline_F2S[dataset]) / baseline_F2S[dataset] * 100
			improvement_S2F = (test_acc_S2F - baseline_S2F[dataset]) / baseline_S2F[dataset] * 100
			print('Improvement --> [F-->S]: {:.2f}% [S-->F]: {:.2f}%'.format(improvement_F2S, improvement_S2F))

if __name__ == '__main__':

	args = parser.parse_args()
	print('Dataset : {}\n'.format(args.dataset))
	clf = SAE(args)
	clf.evaluate()
